package com.prpat;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import de.uniba.wiai.lspi.chord.com.local.Registry;
import de.uniba.wiai.lspi.chord.console.command.Exit;
import de.uniba.wiai.lspi.chord.console.command.JoinNetwork;
import de.uniba.wiai.lspi.chord.console.command.LeaveNetwork;
import de.uniba.wiai.lspi.chord.console.command.RemoteChordNetworkAccess;
import de.uniba.wiai.lspi.chord.console.command.ShowEntriesNetwork;
import de.uniba.wiai.lspi.chord.console.command.ShowFingerTableNetwork;
import de.uniba.wiai.lspi.chord.service.PropertiesLoader;
import de.uniba.wiai.lspi.util.console.Command;
import de.uniba.wiai.lspi.util.console.CommandFactory;

public class River 
{
	public static CommandFactory factory;

	public static final int CHAT_PORT = 5000;
	public static final int CHORD_PORT = 4242;
	
	public static final int TIMEOUT;
	
	public static boolean CONNECTED;
	public static final String LOCAL_IP = getLocalHost();
	public static final String BOOTSTRAP;

	public static JFrame connectFrame;
	public static JFrame dialogFrame;

	public static JTextArea display;
	public static JTextField input;
	
	static
	{
		// read property file by invoking static method on ChordImpl
		PropertiesLoader.loadPropertyFile();
		
		BOOTSTRAP = System.getProperty("com.prpat.Globals.bootstrap");
		TIMEOUT = 1000 * Integer.parseInt(System.getProperty("com.prpat.Globals.timeout"));
	}
	
	/**
	 *  
	 */
	public static void initializer()
	{
		suicider();
		if(iAmServer()){
			StateTrans.initialState();
		}

		terminalExec(new String[]{"rm", "-f", "utils/log.log"});
		terminalExec(new String[]{"rm", "-f", "/opt/john-1.7.9-jumbo-6/run/john.log"});
		terminalExec(new String[]{"rm", "-f", "/opt/john-1.7.9-jumbo-6/run/john.pot"});
		terminalExec(new String[]{"rm", "-f", "/opt/john-1.7.9-jumbo-6/run/john.rec"});
		terminalExec(new String[]{"rm", "-rf", "files"});
		terminalExec(new String[]{"mkdir", "files"});
		
		Hashtable<String, String> commandMapping = new Hashtable<String, String>();
		commandMapping.put(Exit.COMMAND_NAME, Exit.class.getName());

		/*
		 * Commands to create a node that connects to a remote chord network.
		 */
		commandMapping.put(JoinNetwork.COMMAND_NAME, JoinNetwork.class.getName());
		commandMapping.put(LeaveNetwork.COMMAND_NAME, LeaveNetwork.class.getName());
		commandMapping.put(ShowEntriesNetwork.COMMAND_NAME, ShowEntriesNetwork.class.getName());
		commandMapping.put(ShowFingerTableNetwork.COMMAND_NAME, ShowFingerTableNetwork.class.getName());

		/* Command for personal use				*/
		commandMapping.put(FindSuccessor.COMMAND_NAME, FindSuccessor.class.getName());
		/* Get the registry for thread communication as this is the object commands need to be executed		*/
		Object toCommand = Registry.getRegistryInstance();

		factory = new CommandFactory(new Object[] { toCommand,
				RemoteChordNetworkAccess.getUniqueInstance() }, System.out,
				commandMapping);
	}
	
	public static void connectionRenewer()
	{
		River.CONNECTED = River.startChord();
		Sender.init();
		if(River.iAmServer())
			StateTrans.newHash();
	}
	
	/**
	 * Execute a command trough Unix terminal<br>
	 * <b>E.g </b>: in order to execute the command <code>ps aux | grep STRING</code> 
	 * the method should be called this way<br> 
	 * <code>terminalExec(new String[]{"ps", "aux", "|", "grep", "STRING"});</code>
	 * @usage 
	 * @param params - params[0] is the command name, rest are command parameters
	 */
	public static String terminalExec(String[] params)
	{
		StringBuilder output = new StringBuilder();
		try 
		{
			Process child = Runtime.getRuntime().exec( params );

			// Get the input stream and read from it the command output
			InputStream in = child.getInputStream();
			InputStream er = child.getErrorStream();
			int c;
			while ((c = in.read()) != -1) 
				output.append((char)c);
			in.close();
			while ((c = er.read()) != -1) 
				output.append((char)c);
			er.close();
		} 
		catch (IOException e) {System.err.println("River, terminalExec, ");}
		return output.toString();     
	}

	/**
	 * kills all the processes using ports 4242 and 5000
	 */
	public static void suicider()
	{
		terminalExec(new String[]{"fuser", "4242/tcp", "-k"});
		terminalExec(new String[]{"fuser", "5000/tcp", "-k"});
	}

	public static boolean ocExecuted(String command) 
	{
		boolean executed = false;
		try 
		{
			if (command == null) 
				command = "";

			command = command.trim();

			Command com = River.factory.createCommand(command);
			com.execute();
			executed = true;
		} 
		catch (Exception e) {
			System.err.println("River, ocExecuted");
		}
		return executed;
	}

	public static boolean startChord()
	{
		boolean connected = false;
		if(iAmServer())
			connected = ocExecuted("joinN");
		else
		{
//			display.append("Waiting for the Bootsrap server to connect\n");
			System.out.println("Waiting for the Bootsrap server to connect\n");
			while(!connected)
			{
				connected = ocExecuted("joinN -bootstrap " + BOOTSTRAP);
				if(!connected)
				{
					try {
						synchronized (River.class) {
							River.class.wait(6000);
						} 
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
		if(connected)
		{
			Thread serverThread = new Thread(new Listener());
			serverThread.setName("PRPAT_Server_thread");
			serverThread.start();
		}

		return connected;
	}

	/**
	 * 
	 * @return the IP address of the Linux machine running this program
	 * @throws java.net.SocketException
	 */
	private static String getLocalHost()
	{
		Enumeration<?> en;
		try 
		{
			en = java.net.NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			e.printStackTrace();
			return "";
		}
		while (en.hasMoreElements()) 
		{
			java.net.NetworkInterface i = (java.net.NetworkInterface) en.nextElement();
			for (Enumeration<InetAddress> en2 = i.getInetAddresses(); en2.hasMoreElements();)
			{
				java.net.InetAddress addr = (java.net.InetAddress) en2.nextElement();
				if (!addr.isLoopbackAddress()) 
				{
					if (addr instanceof java.net.Inet4Address) 
					{
						return addr.getHostAddress();
					}
				}
			}
		}
		return "";
	}

	public static boolean iAmServer() {
		return BOOTSTRAP.equalsIgnoreCase(LOCAL_IP);
	}
}
